"use strict";

const {strict: assert} = require("assert");

const {mock_cjs, mock_esm, zrequire} = require("../zjsunit/namespace");
const {make_stub} = require("../zjsunit/stub");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");

const noop = () => {};

mock_cjs("jquery", $);
const color_data = mock_esm("../../static/js/color_data");
const message_util = mock_esm("../../static/js/message_util");
const stream_color = mock_esm("../../static/js/stream_color");
const stream_list = mock_esm("../../static/js/stream_list");
const stream_muting = mock_esm("../../static/js/stream_muting");
const subs = mock_esm("../../static/js/subs", {
    update_settings_for_subscribed: noop,
});

mock_esm("../../static/js/all_messages_data", {
    all_messages_data: {
        all_messages() {
            return ["msg"];
        },
    },
});
const message_lists = mock_esm("../../static/js/message_lists", {
    current: {},
});
mock_esm("../../static/js/recent_topics", {
    complete_rerender: () => {},
});
mock_esm("../../static/js/settings_notifications", {
    update_page: () => {},
});

mock_esm("../../static/js/overlays", {streams_open: () => true});

const {Filter} = zrequire("../js/filter");
const message_view_header = zrequire("message_view_header");
const narrow_state = zrequire("narrow_state");
const peer_data = zrequire("peer_data");
const people = zrequire("people");
const stream_data = zrequire("stream_data");
const stream_events = zrequire("stream_events");

const george = {
    email: "george@zulip.com",
    full_name: "George",
    user_id: 103,
};
const me = {
    email: "me@zulip.com",
    full_name: "Me Myself",
    user_id: 104,
};
people.add_active_user(george);
people.add_active_user(me);
people.initialize_current_user(me.user_id);

const dev_help = {
    subscribed: true,
    color: "blue",
    name: "dev help",
    stream_id: 2,
    is_muted: true,
    invite_only: false,
};

const frontend = {
    subscribed: false,
    color: "yellow",
    name: "frontend",
    stream_id: 101,
    is_muted: true,
    invite_only: false,
};

function narrow_to_frontend() {
    const filter = new Filter([{operator: "stream", operand: "frontend"}]);
    narrow_state.set_current_filter(filter);
}

function test(label, f) {
    run_test(label, (override) => {
        stream_data.clear_subscriptions();
        f(override);
    });
}

test("update_property", (override) => {
    const sub = {...frontend};
    stream_data.add_sub(sub);

    const stream_id = sub.stream_id;

    // Invoke error for non-existent stream/property
    {
        blueslip.expect("warn", "Update for an unknown subscription");
        stream_events.update_property(99, "color", "blue");
        blueslip.reset();

        blueslip.expect("warn", "Unexpected subscription property type");
        stream_events.update_property(stream_id, "not_real", 42);
        blueslip.reset();
    }

    // Test update color
    {
        const stub = make_stub();
        override(stream_color, "update_stream_color", stub.f);
        stream_events.update_property(stream_id, "color", "blue");
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, "blue");
    }

    // Test in home view
    {
        const stub = make_stub();
        override(stream_muting, "update_is_muted", stub.f);
        stream_events.update_property(stream_id, "in_home_view", false);
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, true);
    }

    function checkbox_for(property) {
        return $(`#${CSS.escape(property)}_${CSS.escape(stream_id)}`);
    }

    // Test desktop notifications
    stream_events.update_property(stream_id, "desktop_notifications", true);
    assert.equal(sub.desktop_notifications, true);
    let checkbox = checkbox_for("desktop_notifications");
    assert.equal(checkbox.prop("checked"), true);

    // Tests audible notifications
    stream_events.update_property(stream_id, "audible_notifications", true);
    assert.equal(sub.audible_notifications, true);
    checkbox = checkbox_for("audible_notifications");
    assert.equal(checkbox.prop("checked"), true);

    // Tests push notifications
    stream_events.update_property(stream_id, "push_notifications", true);
    assert.equal(sub.push_notifications, true);
    checkbox = checkbox_for("push_notifications");
    assert.equal(checkbox.prop("checked"), true);

    // Tests email notifications
    stream_events.update_property(stream_id, "email_notifications", true);
    assert.equal(sub.email_notifications, true);
    checkbox = checkbox_for("email_notifications");
    assert.equal(checkbox.prop("checked"), true);

    // Tests wildcard_mentions_notify notifications
    stream_events.update_property(stream_id, "wildcard_mentions_notify", true);
    assert.equal(sub.wildcard_mentions_notify, true);
    checkbox = checkbox_for("wildcard_mentions_notify");
    assert.equal(checkbox.prop("checked"), true);

    // Test name change
    {
        const stub = make_stub();
        override(subs, "update_stream_name", stub.f);
        stream_events.update_property(stream_id, "name", "the frontend");
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, "the frontend");
    }

    // Test description change
    {
        const stub = make_stub();
        override(subs, "update_stream_description", stub.f);
        stream_events.update_property(stream_id, "description", "we write code", {
            rendered_description: "we write code",
        });
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, "we write code");
    }

    // Test email address change
    stream_events.update_property(stream_id, "email_address", "zooly@zulip.com");
    assert.equal(sub.email_address, "zooly@zulip.com");

    // Test pin to top
    {
        override(stream_list, "refresh_pinned_or_unpinned_stream", noop);
        stream_events.update_property(stream_id, "pin_to_top", true);
        checkbox = checkbox_for("pin_to_top");
        assert.equal(checkbox.prop("checked"), true);
    }

    // Test stream privacy change event
    {
        const stub = make_stub();
        override(subs, "update_stream_privacy", stub.f);
        stream_events.update_property(stream_id, "invite_only", true, {
            history_public_to_subscribers: true,
        });
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.deepEqual(args.val, {
            invite_only: true,
            history_public_to_subscribers: true,
        });
    }

    // Test stream stream_post_policy change event
    {
        const stub = make_stub();
        override(subs, "update_stream_post_policy", stub.f);
        stream_events.update_property(
            stream_id,
            "stream_post_policy",
            stream_data.stream_post_policy_values.admins.code,
        );
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, stream_data.stream_post_policy_values.admins.code);
    }

    // Test stream message_retention_days change event
    {
        const stub = make_stub();
        override(subs, "update_message_retention_setting", stub.f);
        stream_events.update_property(stream_id, "message_retention_days", 20);
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("sub", "val");
        assert.equal(args.sub.stream_id, stream_id);
        assert.equal(args.val, 20);
    }
});

test("marked_unsubscribed (code coverage)", () => {
    // We don't error for unsubscribed streams for some reason.
    stream_events.mark_unsubscribed(undefined);
});

test("marked_(un)subscribed (early return)", () => {
    // The early-return prevents us from exploding or needing
    // to override functions with side effects
    stream_events.mark_subscribed({subscribed: true});
    stream_events.mark_unsubscribed({subscribed: false});
});

test("marked_subscribed (error)", () => {
    // Test undefined error
    blueslip.expect("error", "Undefined sub passed to mark_subscribed");
    stream_events.mark_subscribed(undefined, [], "yellow");
    blueslip.reset();
});

test("marked_subscribed (normal)", (override) => {
    const sub = {...frontend};
    stream_data.add_sub(sub);
    override(stream_data, "subscribe_myself", noop);
    override(stream_color, "update_stream_color", noop);

    narrow_to_frontend();

    let args;
    let list_updated = false;

    const stream_list_stub = make_stub();
    const message_view_header_stub = make_stub();
    const message_util_stub = make_stub();

    override(stream_list, "add_sidebar_row", stream_list_stub.f);
    override(message_util, "do_unread_count_updates", message_util_stub.f);
    override(message_view_header, "render_title_area", message_view_header_stub.f);
    override(message_lists.current, "update_trailing_bookend", () => {
        list_updated = true;
    });

    stream_events.mark_subscribed(sub, [], "blue");

    args = message_util_stub.get_args("messages");
    assert.deepEqual(args.messages, ["msg"]);

    args = stream_list_stub.get_args("sub");
    assert.equal(args.sub.stream_id, sub.stream_id);
    assert.equal(message_view_header_stub.num_calls, 1);

    assert.equal(list_updated, true);

    assert.equal(sub.color, "blue");
    narrow_state.reset_current_filter();
});

test("marked_subscribed (color)", (override) => {
    override(stream_data, "subscribe_myself", noop);
    override(message_util, "do_unread_count_updates", noop);
    override(stream_list, "add_sidebar_row", noop);

    const sub = {
        subscribed: false,
        name: "production help",
        stream_id: 201,
        is_muted: true,
        invite_only: false,
    };

    override(color_data, "pick_color", () => "green");

    // narrow state is undefined
    {
        const stub = make_stub();
        override(subs, "set_color", stub.f);
        blueslip.expect("warn", "Frontend needed to pick a color in mark_subscribed");
        stream_events.mark_subscribed(sub, [], undefined);
        assert.equal(stub.num_calls, 1);
        const args = stub.get_args("id", "color");
        assert.equal(args.id, sub.stream_id);
        assert.equal(args.color, "green");
        blueslip.reset();
    }
});

test("marked_subscribed (emails)", (override) => {
    const sub = {...frontend};
    stream_data.add_sub(sub);
    override(stream_color, "update_stream_color", noop);

    // Test assigning subscriber emails
    // narrow state is undefined
    override(message_util, "do_unread_count_updates", noop);
    override(stream_list, "add_sidebar_row", noop);

    const subs_stub = make_stub();
    override(subs, "update_settings_for_subscribed", subs_stub.f);

    assert(!stream_data.is_subscribed(sub.name));

    const user_ids = [15, 20, 25, me.user_id];
    stream_events.mark_subscribed(sub, user_ids, "");
    assert.deepEqual(new Set(peer_data.get_subscribers(sub.stream_id)), new Set(user_ids));
    assert(stream_data.is_subscribed(sub.name));

    const args = subs_stub.get_args("sub");
    assert.deepEqual(sub, args.sub);
});

test("mark_unsubscribed (update_settings_for_unsubscribed)", (override) => {
    // Test unsubscribe
    const sub = {...dev_help};
    assert(sub.subscribed);

    const stub = make_stub();

    override(subs, "update_settings_for_unsubscribed", stub.f);
    override(stream_list, "remove_sidebar_row", noop);
    override(stream_data, "unsubscribe_myself", noop);

    stream_events.mark_unsubscribed(sub);
    const args = stub.get_args("sub");
    assert.deepEqual(args.sub, sub);
});

test("mark_unsubscribed (render_title_area)", (override) => {
    const sub = {...frontend, subscribed: true};
    stream_data.add_sub(sub);

    // Test update bookend and remove done event
    narrow_to_frontend();
    const message_view_header_stub = make_stub();
    override(message_view_header, "render_title_area", message_view_header_stub.f);
    override(stream_data, "unsubscribe_myself", noop);
    override(subs, "update_settings_for_unsubscribed", noop);
    override(message_lists.current, "update_trailing_bookend", noop);
    override(stream_list, "remove_sidebar_row", noop);

    stream_events.mark_unsubscribed(sub);

    assert.equal(message_view_header_stub.num_calls, 1);

    narrow_state.reset_current_filter();
});

test("remove_deactivated_user_from_all_streams", () => {
    stream_data.add_sub(dev_help);
    const subs_stub = make_stub();
    subs.update_subscribers_ui = subs_stub.f;

    // assert starting state
    assert(!stream_data.is_user_subscribed(dev_help.stream_id, george.user_id));

    // verify that deactivating user should unsubscribe user from all streams
    peer_data.add_subscriber(dev_help.stream_id, george.user_id);
    assert(stream_data.is_user_subscribed(dev_help.stream_id, george.user_id));

    stream_events.remove_deactivated_user_from_all_streams(george.user_id);

    // verify that we issue a call to update subscriber count/list UI
    assert.equal(subs_stub.num_calls, 1);
});
